﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.SharePoint.Administration;
using Microsoft.SharePoint;
using System.Management.Automation.Runspaces;
using System.Collections.ObjectModel;
using System.Management.Automation;
using System.Collections;
using System.Globalization;

namespace ULSLogExtractor
{
    class ULSLogExtractJobDefinition : SPJobDefinition
    {
        private const string SITEURL_PROPERTYNAME = "ULSLogExtractJobDefinition_SiteUrl";
        private static Dictionary<string, string> ParametersDictionary = new Dictionary<string, string>();

        public Uri WebUri
        {
            get
            {
                Uri webUri = new Uri(this.Properties[SITEURL_PROPERTYNAME].ToString());
                return webUri;
            }
            set
            {
                SetTimerJobProperty(SITEURL_PROPERTYNAME, value.AbsoluteUri);
            }
        }

        public string LogFile
        {
            get;
            set;
        }

        public ULSLogExtractJobDefinition()
            : base()
        {
        }

        public ULSLogExtractJobDefinition(SPWeb web)
            : base(Guid.NewGuid().ToString(), web.Site.WebApplication, null, SPJobLockType.None)
        {
            this.WebUri = new Uri(web.Url);
            this.Title = string.Format(CultureInfo.CurrentCulture, "{0} - {1}", this.GetType().Name, this.WebUri);
        }

        public override void Execute(Guid targetInstanceId)
        {
            base.Execute(targetInstanceId);
            ExecuteMergeLog();
        }

        private void ExecuteMergeLog()
        {
            using (SPSite site = new SPSite(this.WebUri.AbsoluteUri))
            {
                using (SPWeb web = site.OpenWeb())
                {
                    ULSLogExtractionList list = new ULSLogExtractionList(web);
                    foreach (ULSLogExtractorListItem item in list.NotCompletedItems)
                    {
                        item.SetStatus(ULSLogExtractionList.JobStatus.InProgress);

                        BuildParametersDictionary(item);

                        try
                        {
                            RunScript();

                            if (System.IO.File.Exists(LogFile))
                            {
                                item.AddAttachement(LogFile);
                                System.IO.File.Delete(LogFile);
                                item.SetStatus(ULSLogExtractionList.JobStatus.Completed);
                            }
                            else
                            {
                                item.SetStatus(ULSLogExtractionList.JobStatus.CompletedNoLog);
                            }
                        }
                        catch (ParameterBindingException exeption)
                        {
                            item.SetStatus(ULSLogExtractionList.JobStatus.Failed, exeption.Message);
                        }
                        catch (SPException exeption)
                        {
                            item.SetStatus(ULSLogExtractionList.JobStatus.Failed, exeption.Message);
                        }
                        finally
                        {
                            lock (ParametersDictionary)
                            {
                                ParametersDictionary.Clear();
                            }
                            LogFile = string.Empty;
                        }
                    }
                }
            }
        }


        private static void BuildParametersDictionary(ULSLogExtractorListItem item)
        {
            AddParameter("-Correlation", item.CorrelationID, false);
            AddParameter("-Message", item.TextToFind, true);
            AddParameter("-Area", item.LogArea, true);
            AddParameter("-Category", item.LogCategory, true);
            AddParameter("-Level", item.LogLevel, false);

            AddDateParameter("-StartTime", item.LogStartDate);
            AddDateParameter("-EndTime", item.LogEndDate);
        }

        private static void AddDateParameter(string parameterName, string parameterValue)
        {
            if (!string.IsNullOrEmpty(parameterValue))
            {
                //string dateCommand = "(Get-Date -Year {0} -Month {1} -Day {2} -Hour {3} -Minute {4} -Second {5})";
                DateTime startTime = Convert.ToDateTime(parameterValue, CultureInfo.InvariantCulture);
                //string dateTimeString = string.Format(dateCommand, startTime.Year, startTime.Month, startTime.Day, startTime.Hour, startTime.Minute, startTime.Second);
                string dateTimeString = startTime.ToString("M/dd/yyyy HH:mm:ss", CultureInfo.InvariantCulture);

                AddParameter(parameterName, dateTimeString, true);
            }
        }


        private static void AddParameter(string parameterName, string parameterValue, bool quotes)
        {
            if (!string.IsNullOrEmpty(parameterValue))
            {
                string parameterValueFormat;
                if (!quotes)
                    parameterValueFormat = "{0}";
                else
                    parameterValueFormat = "'{0}'";
                lock (ParametersDictionary)
                {
                    ParametersDictionary.Add(parameterName, string.Format(CultureInfo.CurrentCulture, parameterValueFormat, parameterValue));
                }
            }
        }


        public void RunScript()
        {
            //create configuration
            PSSnapInException psSnapinException = new PSSnapInException();
            RunspaceConfiguration configuration = RunspaceConfiguration.Create();
            configuration.AddPSSnapIn("Microsoft.SharePoint.Powershell", out psSnapinException);
            Runspace runspace = null;
            Pipeline pipeline = null;

            string tempPath = System.IO.Path.GetTempPath();
            string tempFileName = string.Format(CultureInfo.CurrentCulture, "extract_{0}.csv", DateTime.Now.ToString("HHMmss_ddMyyyy", CultureInfo.InvariantCulture));
            string tempFileFullName = string.Format(CultureInfo.CurrentCulture, "{0}\\{1}", tempPath, tempFileName);

            lock (ParametersDictionary)
            {
                if (psSnapinException == null && ParametersDictionary.Count > 0)
                {
                    try
                    {
                        runspace = RunspaceFactory.CreateRunspace(configuration);
                        runspace.ThreadOptions = PSThreadOptions.ReuseThread;

                        runspace.Open();

                        string filter = BuildFilter(ParametersDictionary);

                        Command getMergeSPLogFileCommand = new Command(string.Format(CultureInfo.CurrentCulture, "Merge-SPLogFile -Path {0} {1}", tempFileFullName, filter), true);

                        pipeline = runspace.CreatePipeline();
                        pipeline.Commands.Add(getMergeSPLogFileCommand);

                        pipeline.Invoke();
                    }
                    finally
                    {
                        if (runspace != null)
                        {
                            pipeline.Dispose();
                            runspace.Dispose();
                        }
                    }
                }
            }

            LogFile = tempFileFullName;

        }

        private static string BuildFilter(Dictionary<string, string> filterDictionary)
        {
            StringBuilder sb = new StringBuilder();
            foreach (KeyValuePair<string, string> entry in filterDictionary)
            {
                sb.Append(string.Format(CultureInfo.CurrentCulture, " {0} {1} ", entry.Key, entry.Value));
            }

            return sb.ToString();
        }

        private void SetTimerJobProperty(string propertyName, string propertyValue)
        {
            bool exists = this.Properties.ContainsKey(propertyName);
            if (exists)
            {
                this.Properties[propertyName] = propertyValue;
            }
            else
            {
                this.Properties.Add(propertyName, propertyValue);
            }
        }
    }
}
